﻿package zephyr.cameracontrol
{
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.KeyboardEvent;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.cameras.FreeCamera3D;
	import flash.ui.Keyboard;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.materials.MovieMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.core.proto.MaterialObject3D;
	import org.papervision3d.render.BasicRenderEngine;
	
	public class FreeCamera3DController extends EventDispatcher 
	{
		
		public var camera:FreeCamera3D;
		public var scene:Scene3D;
		public var viewport:Viewport3D;
		
		private var startPoint:Point;
		
		public var panVelocity:Number = 0;
		public var tiltVelocity:Number = 0;
		public var sensitivity:Number = 10;
		public var friction:Number = 0.1;
		public var threshold:Number = 0.0000001;
		
		private var _autoRotation:Boolean = true;
		private var autoRotateTimer:Timer;
		private var _autoRotateDelay:Number = 15000;
		public var autoRotateIncrement:Number = 15;
		public var isAutoRotating:Boolean = false;
		
		private var isMouseDown:Boolean = false;
		public var panoCubeTree:Array;
		public var materials:MaterialsList;
		
		private var isKeyDown:Boolean = false;
		public var keyIncrement:Number = 75;
		public var keyZoomIncrement:Number = 0.2;
		private var keyUp:Boolean = false;
		private var keyDown:Boolean = false;
		private var keyLeft:Boolean = false;
		private var keyRight:Boolean = false;
		private var keyZoomIn:Boolean = false;
		private var keyZoomOut:Boolean = false;
		public var renderer:BasicRenderEngine;
		
		private var _parent:Object;
		
		//public function FreeCamera3DController( scene:Scene3D, camera:FreeCamera3D, viewport:Viewport3D, panoCubeTree:Array, materials:MaterialsList, renderer:BasicRenderEngine, autoRotation:Boolean = true )
		public function FreeCamera3DController( _parent:Object, autoRotation:Boolean = true )
		{
			this._parent = _parent;
			this.camera = _parent.camera;
			this.scene = _parent.scene;
			this.viewport = _parent.viewport;
			this.panoCubeTree = _parent.panoCubeTree;
			this.materials = _parent.materials;
			this.renderer = _parent.renderer;
			viewport.stage.addEventListener( MouseEvent.MOUSE_DOWN,mouseDownEvent, false, 1, true );
			viewport.stage.addEventListener( MouseEvent.MOUSE_UP,mouseUpEvent, false, 0, true );
			viewport.stage.addEventListener( Event.DEACTIVATE, mouseUpEvent, false, 0, true );
			viewport.stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownEvent, false, 0, true );
			viewport.stage.addEventListener( KeyboardEvent.KEY_UP, keyUpEvent, false, 0, true);
			
			// start the autorotator
			if (autoRotation)
			{
				this.autoRotation = true;
				//override the auto on feature and make it wait for its delay
				//isAutoRotating = false;
			}
		}
		
		protected function mouseDownEvent( event:MouseEvent ):void
		{trace("mouse down");
			for each(  var o:Object in panoCubeTree )
			{
				try {
				var mo3d:MaterialObject3D = materials.getMaterialByName(o.name);
				//much better performance with animation off
				//turn back on smoothing (which was off during the transition
				(mo3d as MovieMaterial).smooth = false;
				}
				catch(e:Error){}
			}
			
			isMouseDown = true;
			
			//reset autorotator on account of user input
			autoRotateTimer.reset();
			autoRotateTimer.stop();
			isAutoRotating = false;
			_autoRotation = false;
			
			startPoint = new Point( viewport.mouseX,viewport.mouseY );
			
			viewport.addEventListener( Event.ENTER_FRAME,enterFrameEvent );
			
			//remove interface render loop
			_parent.viewportInterface.removeEventListener( Event.ENTER_FRAME, _parent.interfaceOnEnterFrame );
		}
		
		protected function keyDownEvent( event:KeyboardEvent ):void
		{ 
			switch( event.keyCode )
			{
			case Keyboard.UP:
				keyUp = true; 
				startKeyMovement();
			break;
	
			case Keyboard.DOWN:
				keyDown = true;
				startKeyMovement();
			break;
	
			case Keyboard.LEFT:
				keyLeft = true;
				startKeyMovement();
			break;
	
			case Keyboard.RIGHT:
				keyRight = true;
				startKeyMovement();
			break;
			case Keyboard.SHIFT:
				keyZoomIn = true;
				startKeyMovement();
			break;
			case Keyboard.CONTROL:
				keyZoomOut = true;
				startKeyMovement();
			break;
			}
		}
		
		protected function startKeyMovement():void
		{
			for each(  var o:Object in panoCubeTree )
			{
				try {
				var mo3d:MaterialObject3D = materials.getMaterialByName(o.name);
				//much better performance with animation off
				//turn back on smoothing (which was off during the transition
				(mo3d as MovieMaterial).smooth = false;
				}
				catch(e:Error){}
			}
			
			//reset autorotator on account of user input
			autoRotateTimer.reset();
			autoRotateTimer.stop();
			isAutoRotating = false;
			_autoRotation = false;
			
			isKeyDown = true;
			
			viewport.addEventListener( Event.ENTER_FRAME,enterFrameEvent );
			_parent.viewportInterface.removeEventListener( Event.ENTER_FRAME, _parent.interfaceOnEnterFrame );
		}
		
		
		protected function mouseUpEvent( event:Event ):void
		{
			isMouseDown = false;
			
			_parent.viewportInterface.addEventListener( Event.ENTER_FRAME, _parent.interfaceOnEnterFrame );
			
			for each(  var o:Object in panoCubeTree )
				{
					try {
					var mo3d:MaterialObject3D = materials.getMaterialByName(o.name);
					//much better performance with animation off
					//turn back on smoothing (which was off during the transition
					(mo3d as MovieMaterial).smooth = true;
					}
					catch(e:Error){}
				}
		}
		
		protected function keyUpEvent(event:KeyboardEvent):void
		{
			switch( event.keyCode )
				{
				case Keyboard.UP:
					keyUp = false;
				break;
	
				case Keyboard.DOWN:
					keyDown = false;
				break;
	
				case Keyboard.LEFT:
					keyLeft = false;
				break;
	
				case Keyboard.RIGHT:
					keyRight = false;
				break;
				case Keyboard.SHIFT:
					keyZoomIn = false;
				break;
				case Keyboard.CONTROL:
					keyZoomOut = false;
				break;
				}
			if ( !keyUp && !keyDown && !keyLeft && !keyRight && !keyZoomIn && !keyZoomOut )
			{
				isKeyDown = false;
				
				_parent.viewportInterface.addEventListener( Event.ENTER_FRAME, _parent.interfaceOnEnterFrame );
				
				for each(  var o:Object in panoCubeTree )
				{
					try {
					var mo3d:MaterialObject3D = materials.getMaterialByName(o.name);
					//much better performance with animation off
					//turn back on smoothing (which was off during the transition
					(mo3d as MovieMaterial).smooth = true;
					}
					catch(e:Error){}
				}

			}
		}
		
		protected function enterFrameEvent( event:Event ):void
		{
			// while mouse is down we calculate new velocities, when it is up we just slow with friction
			if (isMouseDown || isAutoRotating || isKeyDown)
			{
				if (isAutoRotating)
				{
					//if isAutoRotating, spoof the next lines by moving the startpoint
					startPoint = new Point( viewport.stage.mouseX - autoRotateIncrement, viewport.stage.mouseY - camera.rotationX );
					
					//remove interface rendering loop
					_parent.viewportInterface.removeEventListener( Event.ENTER_FRAME, _parent.interfaceOnEnterFrame );
				}
				
				else if (isKeyDown)
				{
					if ( keyUp ) { startPoint = new Point( viewport.stage.mouseX, viewport.stage.mouseY + keyIncrement ); }
					if ( keyDown ) { startPoint = new Point( viewport.stage.mouseX, viewport.stage.mouseY - keyIncrement ); }
					if ( keyLeft) { startPoint = new Point( viewport.stage.mouseX + keyIncrement, viewport.stage.mouseY ); }
					if ( keyRight ) { startPoint = new Point( viewport.stage.mouseX - keyIncrement, viewport.stage.mouseY ); }
					if ( keyZoomIn ) { camera.zoom += keyZoomIncrement }
					if ( keyZoomOut ) { camera.zoom -= keyZoomIncrement }
				}
				
				// calculate new velocities
				panVelocity = (panVelocity - (((startPoint.x - viewport.stage.mouseX) * sensitivity) / 15000));
				tiltVelocity = (tiltVelocity + (((startPoint.y - viewport.stage.mouseY) * sensitivity) / 15000));
			}
			// motion is still over the threshold, so apply friction
			if ( ( (panVelocity * panVelocity) + (tiltVelocity * tiltVelocity) ) > threshold ) {
				// always apply friction so that motion slows AFTER mouse is up
				panVelocity = (panVelocity * (1 - friction) );
				tiltVelocity = (tiltVelocity * (1 - friction) );
			} 
			else 
			{	
				if ( !(isMouseDown) )
				{	
					// motion is under threshold, stop and remove enter frame listener
					panVelocity = 0;
					tiltVelocity = 0;
					viewport.removeEventListener( Event.ENTER_FRAME,enterFrameEvent );
					
				
					//turn on the autoRotator
					autoRotateTimer.reset();
					autoRotateTimer.start();
				}
			}	
			
			// apply new velocities to the camera
			camera.rotationX = camera.rotationX + tiltVelocity;
			camera.rotationY = camera.rotationY + panVelocity;
			
			renderer.renderScene( scene, camera, viewport );
			
		}
		
		// handler fires after autoRotateTimer successfully completes its delay, which is reset by any user input
		private function autoRotateTimerHandler(event:TimerEvent):void
		{	
			viewport.addEventListener( Event.ENTER_FRAME,enterFrameEvent, false, 9 );
			isAutoRotating = true;
			autoRotateTimer.reset();
			autoRotateTimer.stop();
		}
		
		public function set autoRotation(autoRotation:Boolean):void
		{
			if (autoRotation)
			{	
				autoRotateTimer = new Timer(_autoRotateDelay);
				autoRotateTimer.addEventListener("timer", autoRotateTimerHandler, false, 0, true);
				autoRotateTimer.start();
				//isAutoRotating = true;
				_autoRotation = true;
			}
			else
			{
				viewport.stage.removeEventListener( Event.ENTER_FRAME,enterFrameEvent);
				autoRotateTimer.reset();
				autoRotateTimer.stop();
				isAutoRotating = false;
				_autoRotation = false;
			}
		}
		public function get autoRotation():Boolean
		{
			return _autoRotation;
		}
		
		public function autoRotateNow():void
		{trace("auto now");
			autoRotateTimer = new Timer(_autoRotateDelay);
			isAutoRotating = true;
			_autoRotation = true;
		}
		
		public function set autoRotateDelay(autoRotateDelay:Number):void
		{
			if (autoRotateTimer != null)
			{
				autoRotateTimer.delay = autoRotateDelay;
			}
		}
		public function get autoRotateDelay():Number
		{
			return _autoRotateDelay;
		}
	}
}